NLP = Natural Language Processing
L’entreprise « Place de marché » lance un marketplace e-commerce où les vendeurs proposent des articles en postant une photo et une description. Actuellement la catégorisation du faible nombre de produits se fait manuellement. Le site e-commerce a pour objectif de s’étendre et d’agrandir considérablement le nombre de produits et une catégorisation manuelle serait une étape longue et peu fiable. Pour faciliter la mise en ligne de nouveaux articles et la recherche de produits, il devient nécessaire d'automatiser cette tâche. Cette catégorisation se fera à partir des descriptions et des images des produits.
# import packages
from sklearn.naive_bayes import MultinomialNB
from sklearn.ensemble import RandomForestClassifier
from sklearn.neighbors import KNeighborsClassifier
from sklearn.model_selection import train_test_split, GridSearchCV
from sklearn.cluster import DBSCAN, AgglomerativeClustering, KMeans
from sklearn.manifold import TSNE
from sklearn.decomposition import PCA
from sklearn.feature_extraction.text import TfidfVectorizer, CountVectorizer
from sklearn.metrics import silhouette_score, adjusted_rand_score, silhouette_samples
from sklearn.manifold import TSNE
from sklearn.decomposition import PCA
from sklearn.metrics import confusion_matrix
from sklearn.metrics import accuracy_score, precision_score, recall_score, f1_score
import spacy
from sklearn import preprocessing
from nltk.tokenize import word_tokenize
from nltk import FreqDist
from nltk.stem.snowball import SnowballStemmer
from nltk.stem import WordNetLemmatizer
import nltk
import string
from wordcloud import WordCloud, STOPWORDS
from collections import Counter
import collections
from scipy.stats import variation
from scipy import stats
from scipy.stats.stats import pearsonr
import scipy.stats as stats
import seaborn as sns
import matplotlib.pyplot as plt
from matplotlib.pyplot import figure
import matplotlib
import missingno as msno
import pandas as pd
import numpy as np
import re
import math
#from termcolor import colored
from datetime import datetime
from datetime import timedelta
import warnings
warnings.filterwarnings('ignore')
pd.options.mode.chained_assignment = None
pd.options.display.width = 0
%matplotlib inline
import pickle
from termcolor import colored
from matplotlib import cm
# Format & option.
sns.set(rc={'figure.figsize': (16, 9)})
pd.options.display.max_columns = 50
# Style use.
sns.set_style('darkgrid')
plt.style.use('ggplot')
"""from google.colab import drive
drive.mount('/content/drive')
#!ls /content/drive/My\ Drive/*.py
import sys
sys.path.append('/content/drive/My Drive')
from P6_NLP_function import *
Ne fonctionne pas"""
def informations(dataframe):
"""This function gives the general information of a dataset.
It returns the number of rows and columns of the dataset.
dataframe : dataset"""
print(colored("\n Overview of the dataset : \n", 'red'))
print(dataframe.head())
lines = dataframe.shape[0]
columns = dataframe.shape[1]
print(colored("The dataset has {} rows and {} "
"columns. \n \n".format(lines, columns), 'blue'))
print(colored("Column's name : \n", 'green'))
print(dataframe.columns)
print("\n")
print(colored("Column's Type : \n", 'green'))
print(dataframe.dtypes)
print("\n \n")
def pie_NaN(dataframe, size):
"""This function allows to make a pie plot showing the
proportion of missing data on the whole dataset.
dataframe : dataset
size : size of the figure (X,X)"""
lines = dataframe.shape[0]
columns = dataframe.shape[1]
# NAN data
nb_data = dataframe.count().sum()
# Total data = (colonnes*lignes)
nb_totale = (columns*lines)
# Filling rate
rate_dataOK = (nb_data/nb_totale)
print("The data set is filled in at {:.2%}".format(rate_dataOK))
print("and it has {:.2%} of missing data".format(1-rate_dataOK))
print("\n \n ")
# Pie Plot
rates = [rate_dataOK, 1 - rate_dataOK]
labels = ["Données", "NAN"]
explode = (0, 0.1)
colors = ['gold', 'pink']
# Plot
plt.figure(figsize=size)
plt.pie(rates, explode=explode, labels=labels, colors=colors,
autopct='%.2f%%', shadow=True, textprops={'fontsize': 26})
ttl = plt.title("Fill rate of the dataset", fontsize=32)
ttl.set_position([0.5, 0.85])
plt.axis('equal')
# ax.legend(labels, loc = "upper right", fontsize = 18)
plt.tight_layout()
plt.show()
def plot_multiple_histograms(dataframe, size):
"""This function allows you to see graphically
the shape of numerical variables by representing their density.
dataframe : dataser
size : size of the figure (x,x)"""
cols = dataframe.select_dtypes(include='number').columns.tolist()
if cols is None:
print("There are no numeric columns.")
pass
else:
num_plots = len(cols)
num_cols = math.ceil(np.sqrt(num_plots))
num_rows = math.ceil(num_plots/num_cols)
fig, axs = plt.subplots(num_rows, num_cols, figsize=size)
for ind, col in enumerate(cols):
i = math.floor(ind/num_cols)
j = ind - i*num_cols
if num_rows == 1:
if num_cols == 1:
sns.distplot(dataframe[col], kde=True, ax=axs)
else:
sns.distplot(dataframe[col], kde=True, ax=axs[j])
else:
sns.distplot(dataframe[col], kde=True, ax=axs[i, j])
def description_var(df):
"""This function allows the statistical analysis of
numerical variables and the creation of an associated boxplot.
"""
df = data.select_dtypes(include='number')
for col in df:
colData = df[col]
mean = np.mean(colData)
median = np.median(colData)
Q1 = np.percentile(colData, 25)
Q3 = np.percentile(colData, 75)
max = colData.max()
min = colData.min()
variance = np.var(colData)
standard_deviation = np.std(colData)
skew = pd.DataFrame(colData).skew()[0]
kurt = pd.DataFrame(colData).kurtosis()[0]
print("Statistical measures for the variable {} \n" .format(col))
print("The average of the variable {} is : {}".format(
col, round(mean, 2)))
print("The median of the variable {} is : {}".format(
col, round(median, 2)))
print("Quartile Q1 is : {} ".format(round(Q1, 2)))
print("Quartile Q3 is : : {} ".format(round(Q3, 2)))
print("The maximum is : {} ".format(max))
print("The minimum is : {} \n \n".format(min))
print("Measures of dispersion for the variable {} \n" .format(col))
print("The variance of the variable {} is : {} " .format(
col, round(variance, 2)))
print("The standard deviation of the variable {} is : {} " .format(
col, round(standard_deviation, 2)))
print("The coefficient of variation of the variable {} is : {}"
.format(col, round(variation(colData), 2)))
print("The interquartile range of the variable {} is : {} \n \n"
.format(col, round(Q3-Q1, 2)))
print("Shape measures for the variable {}\n" .format(col))
print("The empirical skewness for the variable {} is {} " .format(
col, round(skew, 4)))
if (skew == 0):
print("The distribution of the variable {} is symmetric.\n"
.format(col))
elif (skew > 0):
print("The distribution of the variable {} is spread"
"to the right. \n".format(col))
else:
print("The distribution of the variable {} is spread"
"to the left.\n" .format(col))
print("The empirical Kurtosis for the variable {} is {} " .format(
col, round(kurt, 4)))
if kurt == 0:
print("The distribution of the variable {} has the same smoothness"
"as the normal distribution.\n" .format(col))
elif kurt > 0:
print("The distribution of the variable {} is less flat"
"than the normal distribution, the observations"
"are more concentrated.\n"
.format(col))
else:
print("The distribution of the variable {} is flatter"
" than the normal distribution, the observations"
"are less concentrated.\n"
.format(col))
plt.figure(figsize=(10, 8))
df[col].hist(color='pink', edgecolor='red', log=True, )
plt.title("Statistical representation of the variable {}"
"\n".format(col))
plt.show()
print("Boxplot of the variable {}".format(col))
plt.figure(figsize=(6, 6))
df.boxplot(column=[col], return_type='axes', vert=True,
showfliers=False, showcaps=True, patch_artist=True,
color='tan', medianprops={'linestyle': '-',
'linewidth': 2, 'color': 'red'},
whiskerprops={'linestyle': '-', 'linewidth': 2,
'color': 'blue'},
capprops={'linestyle': '-', 'linewidth': 2,
'color': 'blue'})
plt.show()
print("\n \n")
def get_df_name(df):
name = [x for x in globals() if globals()[x] is df][0]
return name
def graphe_remplissage_variable(dataframe):
"""Distribution of the filling rate of the columns"""
df = pd.DataFrame(round(dataframe.shape[0] - dataframe.isna().sum())/dataframe.shape[0], columns=[
'Taux de remplissage']).sort_values('Taux de remplissage', ascending=False).reset_index()
fig, ax = plt.subplots(figsize=(10, 15))
sns.barplot(y=df['index'], x='Taux de remplissage', data=df)
plt.title('Fill rate of the variables - %', fontsize=30)
plt.show()
def graph_fill_variable(dataframe, size):
"""This function allows to make barplot to show the distribution
of the data between the different columns of the dataset.
dataframe : dataset
size : size of the figure (X, X)
"""
df = pd.DataFrame(round(
dataframe.shape[0] - dataframe.isna().sum())/dataframe.shape[0],
columns=['Filling rate']).sort_values(
'Filling rate', ascending=False).reset_index()
fig, ax = plt.subplots(figsize=size)
sns.barplot(y=df['index'], x='Filling rate', data=df)
plt.title('Fill rate of the variables - %', fontsize=30)
plt.show()
def plot_date_dist(df, feature_w_date):
"""plot the distribution of one feature date
df : dataframe
feature_w_date : Date column to plot"""
df_tmp = pd.DataFrame(pd.DatetimeIndex(df[feature_w_date]).to_period('M'))
tmp = df_tmp[feature_w_date].value_counts()
df_tmp = pd.DataFrame({
feature_w_date: tmp.index,
'Quantity': tmp.values
})
df_tmp = df_tmp.sort_values(by=feature_w_date)
fig, ax = plt.subplots(figsize=(12, 6))
sns.set_color_codes("pastel")
s = sns.barplot(ax=ax,
x=feature_w_date,
y='Quantity',
data=df_tmp)
plt.xticks(rotation=45)
plt.show()
def graphe_objet(dataframe, col, wordcloud=False, barplot=False):
"""This function allows the creation of
WordCloud and histograms
for the most common occurrences.
dataframe : dataset"""
from wordcloud import WordCloud, STOPWORDS
if wordcloud != False:
counts = dataframe[col].value_counts()
counts.index = counts.index.map(str)
wordcloud = WordCloud(
background_color='white',
stopwords=STOPWORDS,
max_words=200,
max_font_size=40,
scale=3,
random_state=5 # chosen at random by flipping a coin; it was heads
).generate_from_frequencies(counts)
fig = plt.figure(1, figsize=(12, 12))
plt.axis('off')
fig.suptitle("WordCloud of the variable {}" .format(col), fontsize=20)
fig.subplots_adjust(top=2.3)
plt.imshow(wordcloud)
plt.show()
print("\n \n")
if barplot != False:
count = dataframe[col].value_counts()
count = count[:20, ]
plt.figure(figsize=(15, 10))
sns.set(style="whitegrid")
sns.barplot(count.values, count.index, alpha=0.8, orient='h')
plt.title("Top 20 number of occurrences for the variable {}" .format(col))
plt.xlabel('Number of Occurrences', fontsize=12)
plt.show()
print("\n \n \n \n \n")
def pie_col_category(dataframe, col, size):
"""This function represents the categorical variables as a pie plot.
dataframe : dataset
size : size of the figure (X,X)"""
values = dataframe[col].value_counts()
labels = dataframe[col].value_counts().index
# Plot
plt.figure(figsize=size)
plt.title("Représentation de la variable {}" .format(
col), fontsize=20)
plt.pie(values, labels=labels,
autopct='%.1f%%', shadow=True, textprops={'fontsize': 20})
plt.axis('equal')
plt.tight_layout()
plt.show()
print("\n \n \n")
def untokenize(words):
"""
Untokenizing a text undoes the tokenizing operation, restoring
punctuation and spaces to the places that people expect them to be.
Ideally, `untokenize(tokenize(text))` should be identical to `text`,
except for line breaks.
"""
text = ' '.join(words)
step1 = text.replace("`` ", '"').replace(
" ''", '"').replace('. . .', '...')
step2 = step1.replace(" ( ", " (").replace(" ) ", ") ")
step3 = re.sub(r' ([.,:;?!%]+)([ \'"`])', r"\1\2", step2)
step4 = re.sub(r' ([.,:;?!%]+)$', r"\1", step3)
step5 = step4.replace(" '", "'").replace(" n't", "n't").replace(
"can not", "cannot")
step6 = step5.replace(" ` ", " '")
return step6.strip()
#text_data['description_tokenize_graph'] = text_data['description_tokenize_clean'].apply(untokenize)
# text_data['description_tokenize_graph_new'] = text_data['description_tokenize_graph'].apply(
# word_tokenize)
def display_scree_plot(pca):
"""
Graph representing the eigenvalues of the pca model based on the file
under study
pca : pca of the model"""
scree = pca.explained_variance_ratio_ * 100
plt.bar(np.arange(len(scree)) + 1, scree)
plt.plot(np.arange(len(scree)) + 1, scree.cumsum(), c="red", marker="o")
plt.xlabel("rank of the axis of inertia")
plt.ylabel("percentage of inertia")
plt.title("Eigenvalue decay") # Eboulis des valeurs propres
plt.show(block=False)
def clustering(algo, data, true_category, label_true_category, algo_name, file_name=None):
print(colored("Exécution \n ", 'red'))
model = algo.fit(data)
if file_name is None:
pass
else :
# save the model to disk
filename = file_name
pickle.dump(model, open(filename, 'wb'))
label = algo.fit_predict(data)
lines = model.labels_.shape[0]
print(colored(
"Nombre de lignes pris en compte pour le clustering {} \n \n".format(lines), 'blue'))
labels = pd.DataFrame(model.labels_, columns=['algo_cluster'])
mylist = model.labels_
mylist = list(set(mylist))
nb_cluster = len(mylist)
print(colored("Qualité du Clustering \n \n ", 'red'))
# mean Silhouette Coefficient of all samples
silhouette = silhouette_score(X=data, labels=model.labels_)
print(colored('Le coefficient de silhouette moyen est de {} pour la méthode {}.\n \n '.format(
silhouette, algo_name), 'blue'))
tot_data_label = pd.concat([true_category, label_true_category, labels], axis=1, names=[
'category', 'label_category', 'algo_cluster'])
tot_data_label.reset_index(drop=True)
# Indice de Rand ajusté.
ARI = adjusted_rand_score(
tot_data_label['label_category'], tot_data_label['algo_cluster'])
print(colored('Le score ARI est de {} pour la méthode {}. \n \n'.format(
ARI, algo_name), 'blue'))
print(colored("Visualisation \n \n ", 'red'))
tsne = TSNE(n_components=2, init="pca")
X_trans = tsne.fit_transform(data)
plt.figure(figsize=(10, 10))
plt.title(
"T-SNE representation of the dataset separation via {}.\n" .format(algo_name), fontsize=30)
sns.scatterplot(
X_trans[:, 0],
X_trans[:, 1],
hue=model.labels_,
legend="full",
palette=sns.color_palette("husl", nb_cluster),
)
sns.set_context("paper", font_scale=1)
plt.xlabel("Feature space for the 1st feature")
plt.xlabel("Feature space for the 2nd feature")
plt.show()
from matplotlib import rcParams
rcParams['axes.titlepad'] = 40
values = tot_data_label['algo_cluster'].value_counts()
labels = tot_data_label['algo_cluster'].value_counts().index
#color = ['gold', 'pink', 'grey', 'orange', 'green', 'blue', 'purple', 'yellow', 'red', 'brown', 'violet']
#colors = color[:nb_cluster]
# Plot
from matplotlib import cm
cs = cm.Set2(np.arange(nb_cluster))
plt.figure(figsize=(10, 10))
plt.pie(values, labels=labels, colors=cs,
autopct='%.2f%%', shadow=True, textprops={'fontsize': 24})
plt.title(
"Répartition des produits entre les différentes catégories déterminées par l'algorithme {}" .format(algo_name), fontsize=30)
plt.axis('equal')
plt.tight_layout()
plt.show()
return silhouette, ARI, tot_data_label
def classification(best_param, X_train, y_train, X_test, y_test, algo_name, file_name=None):
print(colored("Exécution \n ", 'red'))
model = best_param.fit(X_train, y_train)
if file_name is None:
pass
else :
# save the model to disk
filename = file_name
pickle.dump(model, open(filename, 'wb'))
prediction = best_param.predict(X_test)
resume_class = pd.DataFrame(
{'cat_reel': y_test, 'cat_predit': prediction}).reset_index()
print(colored("Qualité de la classification \n \n ", 'red'))
accuracy = accuracy_score(y_test, prediction)
print(colored('L\'accuracy score est de {} pour la méthode {}.\n \n '.format(
accuracy, algo_name), 'blue'))
precision = precision_score(y_test, prediction, average='weighted')
print(colored('Le score de précision est de {} pour la méthode {}.\n \n '.format(
precision, algo_name), 'blue'))
recall = recall_score(y_test, prediction, average='weighted')
print(colored('Le score recall est de {} pour la méthode {}.\n \n '.format(
recall, algo_name), 'blue'))
f1 = f1_score(y_test, prediction, average='weighted')
print(colored('Le score f1 est de {} pour la méthode {}.\n \n '.format(
f1, algo_name), 'blue'))
le = preprocessing.LabelEncoder()
resume_class['label_cat_reel'] = le.fit_transform(
np.array(resume_class['cat_reel']))
resume_class['label_cat_predit'] = le.transform(
np.array(resume_class['cat_predit']))
print(colored("Visualisation \n \n ", 'red'))
plt.figure(figsize=(10, 10))
plt.title('Répartition des catégories après classification par la méthode {}.\n \n '.format(
algo_name), fontsize=18, fontweight="bold")
sns.distplot(resume_class['label_cat_reel'], bins=20, rug=True)
sns.distplot(resume_class['label_cat_predit'], bins=20, rug=True)
plt.xlabel("Catégories")
plt.legend(['reel', 'prediction'])
from matplotlib import cm
fig = plt.figure(figsize=(30, 15))
ax1 = plt.subplot(121)
ax2 = plt.subplot(122)
nb_label_real = resume_class['cat_reel'].nunique()
cs_nb_label_real = cm.Set2(np.arange(nb_label_real))
nb_label_predit = resume_class['cat_predit'].nunique()
cs_nb_label_predit = cm.Set2(np.arange(nb_label_predit))
ax1 = resume_class.groupby('cat_reel').count().plot(kind='pie',
y='label_cat_reel',
ax=ax1,
legend=False,
shadow=True,
startangle=0,
autopct='%1.1f%%',
textprops={
'fontsize': 26},
colors=cs_nb_label_real)
ax1.axis('equal')
ax1.set_xlabel("classes réelles", fontsize=22)
ax1.set_ylabel(" ")
ax2 = resume_class.groupby('cat_predit').count().plot(kind='pie',
y='label_cat_predit',
ax=ax2,
legend=False,
shadow=True,
startangle=0,
autopct='%1.1f%%',
textprops={
'fontsize': 26},
colors=cs_nb_label_predit)
ax2.axis('equal')
ax2.set_xlabel("classes prédites par {}" .format(
algo_name), fontsize=22)
ax2.set_ylabel(" ")
plt.suptitle('Répartition des produits (test data) entre les différentes catégories déterminées',
fontsize=30)
plt.subplots_adjust(wspace=1.5)
plt.show()
cf_matrix = confusion_matrix(
resume_class["cat_reel"], resume_class["cat_predit"], normalize='true')
plt.figure(figsize=(18, 12))
plt.title('Matrice de confusion', size=20, weight='bold')
sns.set(font_scale=1.4) # for label size
sns.heatmap(cf_matrix, annot=True,
fmt='.2%', cmap='BuPu',
annot_kws={"size": 15, 'weight': 'bold'})
plt.ylabel("Vraie catégorie", weight='bold', size=20)
plt.xlabel('Prediction', weight='bold', size=17)
plt.show()
return accuracy, precision, recall, f1, resume_class
from google.colab import drive
drive.mount('/content/drive')
%cd /content/drive/My\Drive/Data_projet_OC
!ls
data = pd.read_csv(
'/content/drive/MyDrive/Data_projet_OC/Flipkart/flipkart_com-ecommerce_sample_1050.csv')
"""data = pd.read_csv(
"/Users/amandinelecerfdefer/Desktop/Formation_Data_Scientist_OC"
"/WORK-projet6/Data/Flipkart/flipkart_com-ecommerce_sample_1050.csv")"""
data.head()
Chaque ligne correspond à une image, avec son nom et ses caractéristiques associées
# %%flake8
list_names = ['data']
datasets = [data]
resume = []
for name in list_names:
pos = list_names.index(name)
dataset = datasets[pos]
print("General presentation of the dataset {}." .format(name))
informations(dataset)
print("Missing Data Rate {}." .format(name))
pie_NaN(dataset, (10, 10))
resume.append(
{
'Dataset': name,
'Lines': dataset.shape[0],
'Columns': dataset.shape[1]
}
)
pd.DataFrame(resume)
df_na = data.isna().sum()/len(data)*100
fig = plt.figure(figsize=(15, 10))
sns.barplot(x=df_na.index, y=df_na.values)
plt.xticks(rotation=90)
plt.xlabel("")
plt.ylabel("%")
plt.title("Pourcentage de données manquantes")
plt.show()
La feature 'brand' contient plus de 30 % de données manquantes mais il y a aussi des données manquantes pour retail_price, discounted_price, product_specification.
# # %%flake8
graph_fill_variable(data, (5, 10))
data.columns
uniq_id : id de chaque image
crawl_timestamp : date d'ajout
product_url : url du produit sur le site
product_name : nom du produit
product_category_tree : catégorie donnée sous forme d'arbre (catégorie, sous-catégorie; sous-sous catégorie, ...) = plusieurs profondeurs de catégorisation
pid : identifiant processus
retail_price : prix au détail de l'article
discounted_price : Prix réduit (promotion, rabais, pour l'achat du produit en grande quantité ?)
image : nom de l'image que l'on peut retrouver dans le dossier regroupant les images.
description : description précise du produit
product_rating : note donnée au produit
overall_rating : note globale
brand : marque
product_specifications : Type de produit
data.describe(include='all')
for name in list_names:
pos = list_names.index(name)
dataset = datasets[pos]
print("Duplicate of the dataset {}." .format(name))
print(dataset.duplicated('uniq_id').sum())
print("\n")
Il n'y a pas de produit en doublon dans le jeu de données.
data.isna().sum(axis=0)
msno.matrix(data)
Il y a des données manquantes pour le prix du produit, les promotions, les marques et la spécificité du produit.
for name in list_names:
pos = list_names.index(name)
dataset = datasets[pos]
lines = dataset.shape[0]
columns = dataset.shape[1]
nb_data = dataset.count().sum()
nb_totale = (columns*lines)
rate_dataOK = (nb_data/nb_totale)
print("NAN of the dataset {}." .format(name))
print("There is {:.2%} of missing data".format(1-rate_dataOK))
print("\n")
Je décide d'imputer les données manquantes du prix des produits et des promotions par la moyenne de chacune de ces colonnes.
data['retail_price'].fillna((data['retail_price'].mean()), inplace=True)
data['discounted_price'].fillna(
(data['discounted_price'].mean()), inplace=True)
Je décide d'imputer les données manquantes de brand et de de product_specifications par "unknow".
data['brand'].fillna('Unknown', inplace=True)
data['product_specifications'].fillna('Unknown', inplace=True)
data.isna().sum(axis=0)
data.dtypes
data['crawl_timestamp'] = data[
'crawl_timestamp'].astype('datetime64')
data['product_name'].nunique()
il y a en tout 1050 produits.
data['retail_price'].nunique()
plusieurs produits ont le même prix
data['discounted_price'].nunique()
plusieurs produits ont le même rabais
print("Analyze of the dataset : {}.".format(get_df_name(data)))
print(description_var(data))
plot_multiple_histograms(data, (15, 5))
df_non_numeric = data.select_dtypes(exclude='number')
df_non_numeric.head(5)
plot_date_dist(df_non_numeric, 'crawl_timestamp')
graphe_objet(df_non_numeric, 'product_category_tree', False, True)
df_non_numeric['product_category_tree'].nunique()
il y a plus de produits que d'arbres de catégories, ce qui veut dire que plusieurs produits ont le même arbre de catégories.
graphe_objet(df_non_numeric, 'product_rating', False, True)
df_non_numeric['is_FK_Advantage_product'].unique()
pie_col_category(df_non_numeric, 'is_FK_Advantage_product', (10, 10))
df_non_numeric['brand'].nunique()
il y a 490 marques représentées dans le jeu de données mais de nombreuses marques sont manquantes et 1 groupe de marques est inconnue.
graphe_objet(df_non_numeric, 'brand', True, True)
# %%flake8
product_by_brand = data[['uniq_id', 'brand']]\
.groupby('brand').count().reset_index()
product_by_brand = product_by_brand.sort_values(by=['uniq_id'],
ascending=False).head(10)
plt.figure(figsize=(15, 10))
sns.barplot(x='brand',
y='uniq_id',
data=product_by_brand)
plt.title("Distribution of product by brand")
plt.show()
La très grande majorité des produits n'a pas de marque connue.
data['product_specifications'].nunique()
Exemple pour un produit :
data.columns
print("Exemple pour le produit : {} \n".format(data['product_name'].iloc[1]))
print("L'arbre de catégorie du produit est : {} \n".format(
data['product_category_tree'].iloc[1]))
print("La marque du produit est : {} \n".format(data["brand"].iloc[1]))
print("Le descriptif du produit est : \n {}".format(
data["product_specifications"].iloc[1]))
data = data.copy()
def remove_columns(dataframe):
new = pd.DataFrame()
columns_to_keep = [
'image', 'product_name', 'product_category_tree', 'description']
for column in columns_to_keep:
try:
new[column] = dataframe[column]
except:
print('...colonne non présente : ', column)
print('\n')
print("All selected columns have been kept from the dataset")
return new
text_data = remove_columns(data)
text_data.head()
La "racine" de chaque arbre de catégorisation sera prise comme base pour vérifier la bonne prédiction des catégories futures.
text_data["product_category_tree"][0]
def recover_root_cat(column):
categorie = column.split('["')[1].split('"]')[0]
cat = categorie.split(' >> ')[0]
return cat
def recover_cat1(column):
categorie = column.split('["')[1].split('"]')[0]
cat = categorie.split(' >> ')[1]
return cat
text_data['category'] = text_data['product_category_tree'].apply(
recover_root_cat)
text_data['manual_cat1'] = text_data['product_category_tree'].apply(
recover_cat1)
del text_data['product_category_tree']
text_data
text_data['category'].unique()
#text_data['category'] = text_data['category'].replace({'a':'b', 'c':'d'})
#text_data['category'] = text_data['category'].apply(lambda x: x.replace('&', 'and'))
text_data['category'] = text_data['category'].str.replace(
'&', 'and')
text_data['category'] = text_data['category'].str.replace(
' ', '_')
text_data['category'] = text_data['category'].str.lower()
text_data['category'].unique()
text_data['manual_cat1'] = text_data['manual_cat1'].str.replace(' ', '_')
text_data['manual_cat1'] = text_data['manual_cat1'].str.lower()
text_data['manual_cat1'].unique()
text_data['category'].nunique()
text_data['manual_cat1'].nunique()
text_data['category'].value_counts()
text_data['manual_cat1'].value_counts()
text_data
producy_by_root = text_data[['product_name', 'category']]\
.groupby('category').count().reset_index()
producy_by_root = producy_by_root.sort_values(by=['product_name'],
ascending=False)
sns.barplot(x='category',
y='product_name',
data=producy_by_root,
palette='spring')
plt.title("Distribution des produits selon leur catégorie",
weight='bold', size=20)
plt.show()
values = text_data[['product_name', 'category']]\
.groupby('category').count()
labels = text_data[['product_name', 'category']]\
.groupby('category').count().index
# Plot
plt.figure(figsize=(15, 15))
plt.pie(values, labels=labels,
autopct='%.2f%%', shadow=True, textprops={'fontsize': 24})
ttl = plt.title(
"Répartition des produits entre les différentes catégories manuelles (root)", fontsize=30)
ttl.set_position([0.5, 0.8])
plt.axis('equal')
plt.tight_layout()
plt.show()
Il y a le même nombre de produit par catégorie racine (150).
plt.title('Distribution of product by precise category',
weight='bold', size=20)
plt.xticks(rotation=45, ha='right', size=10, weight='bold')
sns.barplot(x=text_data.groupby('manual_cat1').size().index,
y=text_data.groupby('manual_cat1').size(),
palette='spring')
plt.xlabel('Catégories produit', weight='bold', size=17)
plt.ylabel('Nbr de produits', weight='bold', size=12)
Il y a en tout 62 catégories précises. Quelques catégories se détachent des autres par un nombre de produits importants entre autre 1 catégorie qui à elle seule regroupe 150 poduits.
del text_data['manual_cat1']
#text_data.set_index('product_name', inplace=True)
étapes à faire :
# on récupère les stopword de WordCloud
#from wordcloud import WordCloud, STOPWORDS
stopwords = list(set(STOPWORDS))
stopwords = [l.lower() for l in list(stopwords)]
# on récupère les lettres isolées
#import string
alphabet_string = list(string.ascii_lowercase)
# on récupère les stopword de nltk
import nltk
nltk.download('stopwords')
english_stop = nltk.corpus.stopwords.words('english')
english_stop = [l.lower() for l in list(english_stop)]
# si choix de RegexpTokenizer, utilisation inutile car non sélectionner à cet étape
# si utilisation de word_tokenize : éléments nécessaire et à ajouter à all_stopword
# on récupère les signes de ponctuation
#import string
ponctuation_string = list(string.punctuation)
# suppression des doublons
all_stopwords = set(stopwords + alphabet_string + english_stop)
# équivaut à
# all_stopwords = list(stopwords + alphabet_string + english_stop)
# suppression des doublons stopwords
#stopwords = []
# for i in all_stopwords:
# if i not in stopwords: stopwords.append(i)
# nb doublon : len(all_stopwords)-len(stopwords)
# WordCLoud d'une partie des Stopwords
from collections import Counter
word_could_dict = Counter(all_stopwords)
wordcloud = WordCloud(background_color='white', width=1000,
height=500).generate_from_frequencies(word_could_dict)
plt.figure(figsize=(15, 8))
plt.imshow(wordcloud)
plt.axis("off")
plt.show()
lemmatization : conservation du sens des mots utilisés par la transformation du mot par sa forme canonique. Par exemple pour un verbe, ce sera son infinitif. Pour un nom, son masculin singulier. On garde donc la richesse du vocabulaire. Fonctionne pour le français et l'anglais.
stemming : conservation de la racine des mots étudiés. L'idée étant de supprimer les suffixes, préfixes des mots afin de ne conserver que leur origine : plus rapide, plus simple mais ne marche que pour l'anglais.
N_grams : pend une suite de mots pour saisir le contexte minimal d'une phrase.
from nltk.stem import WordNetLemmatizer
from nltk.tokenize import word_tokenize
nltk.download('punkt')
nltk.download('wordnet')
def lemmatize_text(text):
lemmatizer = WordNetLemmatizer()
return [lemmatizer.lemmatize(w) for w in text]
def stemmer_text(text):
stemmer = SnowballStemmer("english")
return [stemmer.stem(w) for w in text]
def normalization_df_text(df, column, lemmatizer=False, stemming=False, stopword=None):
# 1: replace description with the lower case version
df[column] = df[column].str.lower()
'''utilisation possible de
text_data['description_tokenize'] = text_data['description'].apply(word_tokenize)
tokenisation simple de chaque ligne du dataframe mais code supplémentaire pour majuscule, ponctuation'''
# 2: sépare chaque string en liste de mots, supprimer caractère numérique, supprimer ponctuation
# on ne garde que les mots avec des uniquement des lettre alphabétique d'une taille strictement supérieure à 4
# supprimer taille 1 ou 2 mais garder mot 3
tokenizer = nltk.RegexpTokenizer(r'[a-z]{4,}')
df['description_tokenize_clean'] = df[column].map(
tokenizer.tokenize)
# 3: lemmatization or stemming
# lemmatization : on garde la forme canonique des mots
if lemmatizer is not False:
df['description_tokenize_clean'] = df['description_tokenize_clean'].apply(
lemmatize_text)
# stemmer = racine du mot
if stemming is not False:
df['description_tokenize_clean'] = df['description_tokenize_clean'].apply(
stemmer_text)
# 4: Suppression StopWord
df['description_tokenize_clean'] = df['description_tokenize_clean'].apply(
lambda x: [item for item in x if item not in stopword])
return df['description_tokenize_clean']
normalization_df_text(text_data, 'description',
True, False, all_stopwords)
del text_data['description']
text_data
text_data
# trouver les 100 mots que l'on retrouve le plus dans toutes les descriptions
#from nltk import FreqDist
#from nltk.tokenize import word_tokenize
lists = text_data['description_tokenize_clean']
words = []
for wordList in lists:
words += wordList
fdist = FreqDist(words)
mostcommon = fdist.most_common(100)
mclist = []
for i in range(len(mostcommon)):
mclist.append(mostcommon[i][0])
words = [w for w in words if w not in mclist]
df = pd.DataFrame(mostcommon)
plt.figure(figsize=(15, 10))
sns.barplot(x=df[0].head(15),
y=df[1].head(15))
plt.title("Distribution of customers by state")
plt.title("Top 15 number of occurrences of words")
plt.xlabel(' ')
plt.ylabel(' ')
plt.show()
"""remove_mostcommon = list(df[0])"""
"""text_data"""
"""text_data['description_untokenize_clean'] = text_data['description_tokenize_clean'].apply(untokenize)"""
"""del text_data['description_tokenize_clean']"""
"""remove_mostcommon[:10]"""
"""normalization_df_text(text_data, 'description_untokenize_clean',
False, False, remove_mostcommon)"""
"""del text_data['description_untokenize_clean']
text_data"""
Encodage des catégories en classes numériques. Il y a 7 classes root différentes car 7 catégories racine.
# Encodage des catégories trouvées manuellement
le = preprocessing.LabelEncoder()
text_data['label_category'] = le.fit_transform(
np.array(text_data['category']))
label = ['0', '1', '2', '3', '4', '5', '6']
for i in range(len(label)):
df = text_data.loc[text_data['label_category'] == i]
cat = df['category'].iloc[0]
print('Le label {} correspond à la catégorie {}' .format(i, cat))
text_data['category'].value_counts()
values = text_data['category'].value_counts()
labels = text_data['category'].value_counts().index
colors = ['gold', 'pink', 'grey', 'orange', 'green', 'blue', 'purple']
explode = (0, 0, 0, 0, 0, 0, 0)
# Plot
plt.figure(figsize=(15, 15))
plt.pie(values, explode=explode, labels=labels, colors=colors,
autopct='%.2f%%', shadow=True, textprops={'fontsize': 24})
ttl = plt.title(
"Répartition des produits entre les différentes catégories manuelles", fontsize=30)
ttl.set_position([0.5, 0.8])
plt.axis('equal')
plt.tight_layout()
plt.show()
text_data['description_untokenize_clean'] = text_data['description_tokenize_clean'].apply(
untokenize)
del text_data['description_tokenize_clean']
del text_data['product_name']
text_data
#y_train.to_csv('y_train.csv', index=False)
# sur google colab
from google.colab import drive
drive.mount('/content/drive')
text_data.to_csv('df_untokenize_description.csv', index=False)
!cp df_untokenize_description.csv /content/drive/My\ Drive/
Donne le nombre d'occurences d'une liste de mots : à chaque fois que l'on croise ce mot, on le met dans le sac correspondant et on fait la somme du nombre de mots. la représentation est un vecteur de fréquence d'apparition des différents mots utilisés.
https://medium.com/@cmukesh8688/tf-idf-vectorizer-scikit-learn-dbc0244a911a
max_df : fréquence maximale que doit avoir un mot, sinon si sa fréquence est supérieure, le mot n'est pas pris en compte.
min_df : fréquence minimale que doit avoir un mot, sinon si sa fréquence est inférieure, le mot n'est pas pris en compte.
max_features : nombre max de mots par sac de mots
from sklearn.feature_extraction.text import CountVectorizer
countvectorizer = CountVectorizer(encoding='utf-8',
strip_accents=None,
lowercase=None,
preprocessor=None,
tokenizer=None,
stop_words=all_stopwords,
analyzer='word',
max_df=0.95,
min_df=0.01)
Les paramètres principaux à spécifier sont max_df et min_df qui correspondent respectivement à la fréquence maximale et minimale d'apparition du feature dans la totalité du texte. La fréquence maximale d'apparition est de 95% et la fréquence minimale d'apparition d'un mot dans le texte est de 1%. Max_features qui correspond au nombre de mots par sac n'est pas nécessaire ici car le nombre de données étant grand, il n'y a pas de contraintes physiques.
# convert the dataframe's column into a matrix
count_wm = countvectorizer.fit_transform(
text_data['description_untokenize_clean'])
# retrieve the terms found in the corpora
count_tokens = countvectorizer.get_feature_names()
len(count_tokens)
Avec cette méthode, 445 tokenizes ont été trouvés.
df_countvect = pd.DataFrame(data=count_wm.toarray(),
index=text_data.index,
columns=count_tokens)
print("Count Vectorizer\n")
df_countvect
Le calcul tf-idf (term frequency–inverse document frequency) permet de calculer un score de proximité entre un terme de recherche et un document.
La partie tf calcule une fonction croissante de la fréquence du terme de recherche dans le document étudié, la partie idf calcule une fonction inversement proportionnelle à la fréquence du terme dans l’ensemble des documents (ou corpus).
Le score total, obtenu en multipliant les deux composantes, permet ainsi de donner un score d’autant plus élevé que le terme est surréprésenté.
from sklearn.feature_extraction.text import TfidfVectorizer
Nettoyage déjà fait donc la plupart des paramètres sont None.
max_df : fréquence maximale que doit avoir un mot, sinon si sa fréquence est supérieure, le mot n'est pas pris en compte.
min_df : fréquence minimale que doit avoir un mot, sinon si sa fréquence est inférieure, le mot n'est pas pris en compte.
# instantiate the vectorizer object
tfidfvectorizer = TfidfVectorizer(encoding='utf-8',
strip_accents=None,
lowercase=True,
preprocessor=None,
tokenizer=None,
analyzer='word',
stop_words=all_stopwords,
max_df=0.95,
min_df=0.01)
Les paramètres principaux à spécifier sont max_df et min_df qui correspondent respectivement à la fréquence maximale et minimale d'apparition du feature dans la totalité du texte. La fréquence maximale d'apparition est de 95% et la fréquence minimale d'apparition d'un mot dans le texte est de 1%.
In Scikit-Learn, the resulting TF-IDF vectors are then normalized by the Euclidean norm.
# convert the dataframe's column into a matrix
tfidf_wm = tfidfvectorizer.fit_transform(
text_data['description_untokenize_clean'])
# retrieve the terms found in the corpora
# if we take same parameters on both Classes(CountVectorizer and TfidfVectorizer),
# it will give same output of get_feature_names() methods)
tfidf_tokens = tfidfvectorizer.get_feature_names()
len(tfidf_tokens)
Avec cette méthode, 445 tokenizes ont été trouvés.
df_tfidfvect = pd.DataFrame(data=tfidf_wm.toarray(),
index=text_data.index,
columns=tfidf_tokens)
print("\nTD-IDF Vectorizer\n")
df_tfidfvect_total = pd.concat([text_data['image'], text_data['category'], df_tfidfvect], axis=1)
df_tfidfvect_total
Pour la suite des analyses, je décide de me baser sur la méthode tf-idf qui est la plus utilisée.
# sur google colab
from google.colab import drive
drive.mount('/content/drive')
df_tfidfvect_total.to_csv('df_tfidfvect_total.csv', index=False)
!cp df_tfidfvect_total.csv /content/drive/My\ Drive/
# sur google colab
from google.colab import drive
drive.mount('/content/drive')
df_tfidfvect.to_csv('df_tfidfvect.csv', index=False)
!cp df_tfidfvect.csv /content/drive/My\ Drive/
"""df_tfidfvect.to_csv('df_tfidfvect.csv', index=False)"""
from google.colab import drive
drive.mount('/content/drive')
%cd /content/drive/My\ Drive/
!ls
df_tfidfvect = pd.read_csv('/content/drive/MyDrive/'
'df_tfidfvect.csv')
"""df_visual_word = pd.read_csv(
"/Users/amandinelecerfdefer/Desktop/Formation_Data_Scientist_OC"
"/WORK-projet6/df_tfidfvect.csv")"""
On réalise dans cette partie un clusterig via 3 algorithmes:
Nous allons comparer les clusters obtenus et surtout le clustering sur sa qualité (coefficent de silhouette), mais aussi comparer les clusters obtenus par rapport aux classes réelles des objets grâce à l'indice de Rand ajusté (ARI). On produira des visualisations dans les plans factoriels et via T-SNE.
df_tfidfvect.shape
pca = PCA(n_components=df_tfidfvect.shape[1], random_state=40)
df = pca.fit_transform(df_tfidfvect)
plt.figure(figsize=(25, 10))
display_scree_plot(pca)
plt.figure(figsize=(16, 9))
plt.ylabel("Variance cumulée", weight='bold', size=20)
plt.xlabel('Nbr de composantes principales', weight='bold', size=17)
plt.plot(pca.explained_variance_ratio_.cumsum())
plt.title('Courbe de variance cumulée', size=18, weight='bold')
plt.plot([0, df_tfidfvect.shape[1]], [0.95, 0.95])
c = 0
for i in pca.explained_variance_ratio_.cumsum():
c += 1
if(i > 0.95):
print(
'Il faut {} composantes pour expliquer 95% de la variance du dataset'.format(c))
break
composantes = pca.components_
nbre_composantes = pca.n_components
nbre_composantes
a_show = pca.transform(df_tfidfvect)[:, 0] # Component 1
b_show = pca.transform(df_tfidfvect)[:, 1] # Component 2
X_pca = pca.transform(df_tfidfvect)[:, :c]
sns.set_context("paper", font_scale=1.5)
plt.figure(figsize=(15, 15))
chaine = "Plan factoriel pour les composantes 1 et 2 de l'ACP"
plt.title(chaine)
plt.xlabel("Component 1")
plt.ylabel("Component 2")
ax = plt.gca()
sns.scatterplot(
x=a_show,
y=b_show,
hue=text_data['category'],
alpha=0.8,
s=30
)
sns.set_context("paper", font_scale=1)
tsne = TSNE(n_components=2, init="pca")
X_tsne = tsne.fit_transform(X_pca)
df_tsne = pd.DataFrame(X_tsne[:,0:2], columns=['tsne1', 'tsne2'])
df_tsne["class"] = text_data["category"]
print(df_tsne.shape)
plt.figure(figsize=(15,15))
sns.scatterplot(
x="tsne1", y="tsne2", hue="class", data=df_tsne, legend="brief",
palette=sns.color_palette('tab10', n_colors=7), s=50, alpha=0.6)
plt.title('TSNE selon les vraies classes', fontsize = 30, pad = 35, fontweight = 'bold')
plt.xlabel('tsne1', fontsize = 26, fontweight = 'bold')
plt.ylabel('tsne2', fontsize = 26, fontweight = 'bold')
plt.legend(prop={'size': 14})
plt.show()
km = KMeans(n_clusters=7,
random_state=10,
init='k-means++')
model = km.fit(X_pca)
# save the model to disk
filename = 'clustering_model_kmeans_description.sav'
pickle.dump(model, open(filename, 'wb'))
label = km.fit_predict(X_pca)
model.labels_.shape
Les 1050 produits ont bien eu une catégorie (cluster) attribuée par la méthode k-means.
mylist = model.labels_
mylist = list(set(mylist))
mylist
labels_kmeans = pd.DataFrame(model.labels_, columns=['k_means_cluster'])
labels_kmeans.head()
labels_kmeans["k_means_cluster"].unique()
# mean Silhouette Coefficient of all samples
silhouette_kmeans = silhouette_score(X=X_pca, labels=model.labels_)
print('Le coefficient de silhouette moyen est de {} pour la méthode K-means'.format(silhouette_kmeans))
La valeur de la silhouette mesure la similitude d'un objet avec son propre cluster (cohésion) par rapport à d'autres clusters (séparation). La valeur de la silhouette est comprise entre [1, -1], où une valeur élevée indique que l'objet est bien adapté à son propre cluster et mal adapté aux clusters voisins.
Ici le coefficient de silhouette est positif mais assez proche de 0, ce qui permet de dire que les points sont globalement bien répartis entre les clusters et que les clusters sont proches les uns des autres (valeur proche de 0).
Création d'un nouveau dataframe composé du dataframe avec toutes les données associées à leurs clusters déterminés par Kmeans.
tot_data_label = pd.concat(
[text_data['category'], text_data['label_category'], labels_kmeans], axis=1)
tot_data_label
# Indice de Rand ajusté.
ARI_kmeans = adjusted_rand_score(
tot_data_label['label_category'], tot_data_label['k_means_cluster'])
print('Le score ARI est de {} pour la méthode K-means'.format(ARI_kmeans))
L'indice de Rand est la proportion de paires de points (x1,x2) qui sont groupées de la même façon dans les deux partitions. Cet index ajusté est proche de 0 pour un clustering aléatoire et égal à 1 uniquement quand le clustering correspond exactement à la partition initiale.
Donc, plus ce score est grand, plus la prédiction corrrespondra à la répartition des catégories déterminées manuellement.
Ici, cet indice est de 0.25, ce qui veut dire que le clustering a été réalisé un peu aléatoirement.
sns.set_context("paper", font_scale=1.5)
plt.figure(figsize=(15, 10))
chaine = "Factorial design for the first 2 components of the PCA"
plt.title(chaine)
plt.xlabel('Component 1')
plt.ylabel('Component 2')
ax = plt.gca()
sns.scatterplot(x=a_show,
y=b_show,
hue=tot_data_label["k_means_cluster"],
alpha=1, s=20, legend='full',
palette=sns.color_palette("husl", 7))
sns.set_context("paper", font_scale=1)
tsne = TSNE(n_components=2, init="pca")
X_trans = tsne.fit_transform(X_pca)
plt.figure(figsize=(10, 10))
plt.title("T-SNE representation of the dataset separation via "
"KMeans (7 clusters)")
sns.scatterplot(
X_trans[:, 0],
X_trans[:, 1],
hue=model.labels_,
legend="full",
palette=sns.color_palette("husl", 7),
)
sns.set_context("paper", font_scale=1)
plt.xlabel("Feature space for the 1st feature")
plt.xlabel("Feature space for the 2nd feature")
plt.show()
tot_data_label['k_means_cluster'].value_counts()
from matplotlib import rcParams
rcParams['axes.titlepad'] = 40
values = tot_data_label['k_means_cluster'].value_counts()
labels = tot_data_label['k_means_cluster'].value_counts().index
#colors = ['gold', 'pink', 'grey', 'orange', 'green', 'blue', 'red']
#colors = ['#FBDD7E', '#04D8B2','#929591','#7BC8F6','#FFC0CB', '#C79FEF','#FFA500']
nb_cluster = len(tot_data_label['k_means_cluster'].value_counts().index)
print(nb_cluster)
cs = cm.Set2(np.arange(nb_cluster))
explode = (0, 0, 0, 0, 0, 0, 0)
# Plot
plt.figure(figsize=(15, 15))
plt.pie(values, explode=explode, labels=labels, colors=cs,
autopct='%.2f%%', shadow=True, textprops={'fontsize': 24})
plt.title(
"Répartition des produits entre les différentes catégories déterminées par l'algorithme", fontsize=30)
plt.axis('equal')
plt.tight_layout()
plt.show()
On peut voir que la répartitions des données dans les clusters déterminés par k-means est déséquilibrée comparé à la répartition des données par la détermination manuelle des colonnes. Voyons, pour chaque cluster, quelles sont les catégories déterminées manuellement que l'on retrouve dans chaque cluster et qui ont donc migrées dans d'autres clusters.
label = np.arange(tot_data_label['k_means_cluster'].min(
), tot_data_label['k_means_cluster'].max()+1, 1)
for i in range(len(label)):
df = tot_data_label.loc[tot_data_label['k_means_cluster'] == label.item(i)]
cat = df['category'].unique()
print('Dans le Cluster {} on retrouve des données réparties dans les catégories {} déterminées manuellement. \n \n' .format(
label.item(i), cat)) # ['0', '1', '2', '3', '4', '5', '6']
table = pd.pivot_table(tot_data_label, values='label_category', index=['category'], # , 'category'
columns=['k_means_cluster'], aggfunc=lambda x: len(x), fill_value=0)
table
plt.figure(figsize=(18, 12))
plt.title('Répartition des clusters', size=20, weight='bold')
sns.set(font_scale=1.4) # for label size
sns.heatmap(table, annot=True,
cmap='BuPu',
annot_kws={"size": 15, 'weight': 'bold'},
fmt='g')
plt.ylabel("catégorie", weight='bold', size=20)
plt.xlabel('cluster', weight='bold', size=17)
plt.show()
clustering_resume = pd.DataFrame({})
clustering_resume = clustering_resume.append(
pd.DataFrame(
{
"Algorithme": "Kmeans",
"silhouette": silhouette_kmeans,
"ARI": ARI_kmeans
},
index=[0],
)
)
clustering_resume
hierarchique = AgglomerativeClustering(n_clusters=7)
silhouette, ARI, tot_data_label = clustering(
hierarchique, X_pca, text_data['category'], text_data['label_category'], 'clustering hiérarchique')
Ici le coefficient de silhouette est positif mais assez proche de 0, ce qui permet de dire que les points sont globalement bien répartis entre les clusters et que les clusters sont proches les uns des autres (valeur proche de 0). Le clustering par la méthode du clustering hiérarchique est moins bon que celui par la méthode k-means.
Ici, l'indice ARI est de 0.22, ce qui veut dire que le clustering a été réalisé un peu aléatoirement. Ce clustering est moins efficace que le clustering par la méthode k-means.
label = np.arange(tot_data_label['algo_cluster'].min(
), tot_data_label['algo_cluster'].max()+1, 1)
for i in range(len(label)):
df = tot_data_label.loc[tot_data_label['algo_cluster'] == label.item(i)]
cat = df['category'].unique()
print('Dans le Cluster {} on retrouve des données réparties dans les catégories {} déterminées manuellement. \n \n' .format(label.item(i), cat))
table = pd.pivot_table(tot_data_label, values='label_category', index=['category'], # , 'category'
columns=['algo_cluster'], aggfunc=lambda x: len(x), fill_value=0)
table
plt.figure(figsize=(18, 12))
plt.title('Répartition des clusters', size=20, weight='bold')
sns.set(font_scale=1.4) # for label size
sns.heatmap(table, annot=True,
cmap='BuPu',
annot_kws={"size": 15, 'weight': 'bold'},
fmt='g')
plt.ylabel("catégorie", weight='bold', size=20)
plt.xlabel('cluster', weight='bold', size=17)
plt.show()
clustering_resume = clustering_resume.append(
pd.DataFrame(
{
"Algorithme": "clustering hiérarchique",
"silhouette": silhouette,
"ARI": ARI
},
index=[1],
)
)
clustering_resume
Dans notre cas, le clustering devrait être fait par k-means qui donne les meilleurs résultats (silhouette max et ARI max). Mais il faut tout de même trouver un algorithme qui donnera de meilleurs résultats car ici le clustering est peu efficace.
On réalise dans cette partie une classification via 3 algorithmes:
Nous allons comparer les clusters obtenus ainsi que la classification sur sa qualité par le score de l'accuracy, de la précision mais aussi comparer les clusters obtenus par rapport aux classes réelles des objets grâce à l'indice de Rand ajusté (ARI). On produira des visualisations par pie plot.
fichier avec les descriptions nettoyées
text_data.head()
X = text_data.copy()
y = X['category']
X = X.drop(['category'], axis=1)
X = X[X.columns]
# Split en training/testing set.
X_train, X_test, y_train, y_test = train_test_split(X, y,
test_size=0.2 # 20% des données dans le jeu de test = 20% de short data
, random_state=42)
print(X_train.shape, X_test.shape, y_train.shape, y_test.shape)
tfidfvectorizer = TfidfVectorizer(encoding='utf-8',
strip_accents=None,
lowercase=True,
preprocessor=None,
tokenizer=None,
analyzer='word',
stop_words=all_stopwords,
max_df=0.95,
min_df=0.01)
X_train.head()
# convert the dataframe's column into a matrix
X_train_tfidf = tfidfvectorizer.fit_transform(
X_train['description_untokenize_clean'])
X_train_tokens = tfidfvectorizer.get_feature_names()
len(X_train_tokens)
df_tfidf_Xtrain = pd.DataFrame(data=X_train_tfidf.toarray(),
index=X_train.index,
columns=X_train_tokens)
print("\nTD-IDF Vectorizer train test\n")
df_tfidf_Xtrain
X_test.head()
# convert the dataframe's column into a matrix
X_test_tfidf = tfidfvectorizer.transform( # transform se base sur un modèle dejà créé alors que fit_tansform recalcul le modèle
X_test['description_untokenize_clean'])
# retrieve the terms found in the corpora
# if we take same parameters on both Classes(CountVectorizer and TfidfVectorizer),
# it will give same output of get_feature_names() methods)
X_test_tokens = tfidfvectorizer.get_feature_names()
len(X_test_tokens)
df_tfidf_Xtest = pd.DataFrame(data=X_test_tfidf.toarray(),
index=X_test.index,
columns=X_test_tokens)
print("\nTD-IDF Vectorizer\n")
df_tfidf_Xtest
df_tfidf_Xtrain.shape
pca = PCA(n_components=df_tfidf_Xtrain.shape[1], random_state=40)
pca.fit_transform(df_tfidf_Xtrain)
X_pca_train = pca.transform(df_tfidf_Xtrain)[:, :c]
X_pca_test = pca.transform(df_tfidf_Xtest)[:, :c]
# GridSearch
# create new a knn model
knn2 = KNeighborsClassifier()
# create a dictionary of all values we want to test for n_neighbors
param_grid = {'n_neighbors': np.arange(1, 25)}
score = 'accuracy'
# use gridsearch to test all values for n_neighbors
knn_gscv = GridSearchCV(knn2, param_grid, cv=5)
# Fit sur le training set.
knn_gscv.fit(X_pca_train, y_train)
# Afficher le(s) hyperparamètre(s) optimaux
best_param = knn_gscv.best_estimator_
print("Best paramètres: {}".format(best_param))
# Afficher les performances correspondantes
print("Résultats de la validation croisée :")
for mean, std, params in zip(
knn_gscv.cv_results_['mean_test_score'], # score moyen
knn_gscv.cv_results_['std_test_score'], # écart-type du score
knn_gscv.cv_results_['params'] # valeur de l'hyperparamètre
):
print("{} = {:.3f} (+/-{:.03f}) for {}".format(
score,
mean,
std*2,
params
))
accuracy, precision, recall, f1, resume_class = classification(
best_param, X_pca_train, y_train, X_pca_test, y_test, 'knn PCA')
Score d'exactitude de la classification.
Dans la classification multi-label, cette fonction calcule la précision du sous-ensemble : l'ensemble des étiquettes prédites pour un échantillon doit correspondre exactement à l'ensemble correspondant des étiquettes dans y_true. Plus ce score est grand et plus la prédiction des catégories des produits correspond aux catégories déterminées manuellement. Il est donc à maximiser. Il indique le pourcentage de bonnes prédictions. Ici le score d'accuracy est de 0.86 ce qui montre que la classification fonctionne bien avec une grande précision.
La précision est la capacité du classificateur à ne pas étiqueter un produit avec la mauvaise catégorie. Ici le score de précision est de 0.86 montre que la classification est assez précise car le score est proche de 1.
Le rappel est le rapport tp / (tp + fn) où tp est le nombre de vrais positifs et fn le nombre de faux négatifs. Il correspond à la part de faux négatifs. La meilleure valeur est 1 et la plus mauvaise valeur est 0. Ici le score de recall est de 0.86 montre que la classification fait en majorité des vrais positifs car le score est proche de 1.
Le score F1 peut être interprété comme une moyenne pondérée de la précision et du recall, où un score F1 atteint sa meilleure valeur à 1 et son plus mauvais score à 0. La formule pour le score F1 est la suivante : F1 = 2 (précision rappel) / (précision + rappel)
Les labels des catégories prédites correspondent aux labels des catégories réelles pour les donnéees test. Avec ce graphique, on peut voir que les catégories prédites sont proches de la classification manuelle. On peut voir aussi que pour la categorie 4, la classification par knn regroupe moins de produits dans ces catégorie par rapport à la réalité. La conclusion inverse peut être faîte pour les categories 2 et 5. Pour les catégories 0, 1, 3 et 6, la classification par knn regroupe autant de produits dans ces catégories par rapport à la réalité.
L'algorithme fait des erreurs lors de la classification des catégories 0, 3, 4.
classification_resume_PCA = pd.DataFrame({})
classification_resume_PCA = classification_resume_PCA.append(
pd.DataFrame(
{
"Algorithme": "knn",
"accuracy": accuracy,
"precision": precision,
"recall": recall,
"f1_score": f1
},
index=[0],
)
)
classification_resume_PCA
# GridSearch
param_grid = [{'max_depth': range(2, 10, 2)}]
gridSearch = GridSearchCV(RandomForestClassifier(),
param_grid, cv=5, scoring='accuracy')
score = 'accuracy'
# Fit sur le training set.
gridSearch.fit(X_pca_train, y_train)
# Afficher le(s) hyperparamètre(s) optimaux
best_param = gridSearch.best_estimator_
print("Best paramètres: {}".format(best_param))
# Afficher les performances correspondantes
print("Résultats de la validation croisée :")
for mean, std, params in zip(
gridSearch.cv_results_['mean_test_score'], # score moyen
gridSearch.cv_results_['std_test_score'], # écart-type du score
gridSearch.cv_results_['params'] # valeur de l'hyperparamètre
):
print("{} = {:.3f} (+/-{:.03f}) for {}".format(
score,
mean,
std*2,
params
))
accuracy, precision, recall, f1, resume_class = classification(
best_param, X_pca_train, y_train, X_pca_test, y_test, 'Random Forest PCA', 'classification_model_random_forest_description.sav')
Ici le score d'accuracy est de 0.909 ce qui montre que la classification fonctionne bien avec une grande précision.
Ici le score de précision est de 0.910 montre que la classification est assez précise car le score est proche de 1.
Ici le score de recall est de 0.909 montre que la classification fait en majorité des vrais positifs car le score est proche de 1.
Les labels des catégories prédites correspondent aux labels des catégories réelles pour les donnéees test. Avec ce graphique, on peut voir que les catégories prédites sont proches de la classification manuelle. On peut voir aussi que pour les categories 0 et 4, la classification regroupe moins de produits dans ces catégorie par rapport à la réalité. La conclusion inverse peut être faîte pour les catégories 2, 3, 5 et 6. Pour la catégorie 1, la classification par random forest regroupe autant de produits dans cette catégorie par rapport à la réalité.
L'algorithme a plus de mal à prédire la catégorie 0.
classification_resume_PCA = classification_resume_PCA.append(
pd.DataFrame(
{
"Algorithme": "Random Forest",
"accuracy": accuracy,
"precision": precision,
"recall": recall,
"f1_score": f1
},
index=[1],
)
)
classification_resume_PCA
Cet algorithme ne peut pas fonctionner avec des valeurs négatives. Or ici il y en a, c'est pourquoi je décide de transformer mes valeurs et de les mettre à la même échelle dans l'intervalle [0,1].
from sklearn.preprocessing import MinMaxScaler
scaler = MinMaxScaler()
# transform data
X_pca_train = scaler.fit_transform(X_pca_train)
X_pca_test = scaler.fit_transform(X_pca_test)
Classificateur Naive Bayes pour les modèles multinomiaux
Les méthodes de Bayes naïves sont un ensemble d'algorithmes d'apprentissage supervisé basés sur l'application du théorème de Bayes (probabilités conditionnelles) avec l'hypothèse "naïve" d'indépendance conditionnelle entre chaque paire de caractéristiques étant donné la valeur de la variable de classe.
Le classificateur Naive Bayes multinomial convient à la classification avec des caractéristiques discrètes (par exemple, le nombre de mots pour la classification de textes). La distribution multinomiale requiert normalement des nombres entiers de caractéristiques. Cependant, dans la pratique, des comptes fractionnaires tels que tf-idf peuvent également fonctionner.
Le problème de classification revient à estimer la probabilité de chaque classe sachant un vecteur de caractéristiques. Par exemple, on veut estimer la probabilité d’un animal étant: un chien, un chat, une vache ou autre (4 classes) en utilisant quelques caractéristiques: poids, longueur, longueur des pattes et le type de nourriture.
Les probabilités calculées servent à sélectionner la classe la plus probable sachant un vecteur de caractéristiques donné. Donc, la classe estimée (c ) est celle qui maximise la probabilité conditionnelle. la probabilité d’apparition d’une classe c-i est estimée comme étant le nombre de ses échantillons divisé par le nombre total des échantillons d’entrainement.
# GridSearch
param_grid = {'alpha': [0.0001, 0.001, 0.01, 0.1, 0.5, 1.0, 10.0]
}
gridSearch = GridSearchCV(MultinomialNB(), param_grid,
cv=5, scoring='accuracy')
score = 'accuracy'
# Fit sur le training set.
gridSearch.fit(X_pca_train, y_train)
# Afficher le(s) hyperparamètre(s) optimaux
best_param = gridSearch.best_estimator_
print("Best paramètres: {}".format(best_param))
# Afficher les performances correspondantes
print("Résultats de la validation croisée :")
for mean, std, params in zip(
gridSearch.cv_results_['mean_test_score'], # score moyen
gridSearch.cv_results_['std_test_score'], # écart-type du score
gridSearch.cv_results_['params'] # valeur de l'hyperparamètre
):
print("{} = {:.3f} (+/-{:.03f}) for {}".format(
score,
mean,
std*2,
params
))
accuracy, precision, recall, f1, resume_class = classification(
best_param, X_pca_train, y_train, X_pca_test, y_test, 'MultinomialNB PCA')
Ici le score d'accuracy est de 0.89 ce qui montre que la classification fonctionne bien avec une grande précision.
Ici le score de précision est de 0.90 montre que la prédiction est assez précise car le score est proche de 1.
Ici le score de recall est de 0.89 montre que la prédiction fait en majorité des vrais positifs car le score est proche de 1.
Les labels des catégories prédites correspondent aux labels des catégories réelles pour les donnéees test. Avec ce graphique, on peut voir que les catégories prédites sont proches de la classification manuelle. On peut voir aussi que pour les categories 0 et 2, la classification regroupe moins de produits dans ces catégorie par rapport à la réalité. La conclusion inverse peut être faîte pour les catégories 1, 3, 4 et 5. L'algorithme de prédiction associe à la catégorie 6 le même nombre de produit.
L'algorithme fait des erreurs lors de la classification des catégories 0 et 2.
classification_resume_PCA = classification_resume_PCA.append(
pd.DataFrame(
{
"Algorithme": "MultinomialNB",
"accuracy": accuracy,
"precision": precision,
"recall": recall,
"f1_score": f1
},
index=[2],
)
)
classification_resume_PCA
Le clustering trouve bien des clusters pour séparer les données, il peut également être intéressant de voir l'effet d'une classification.
Les algorithmes les mieux adaptés à nos données pour une classification est l'algorithmes du random forest avec une PCA ainsi que l'algorithme Multinomial NB (qui est mieux adapté à des données transfomées par CountVectorizer ou tfidf) avec une précision de 90% dans la prédiction des catégories pour chaque produit (très bonne exactitude dans la classification et très grande capacité à ne pas donner la mauvaise catégorie avec 80% des catégories prédites exactes par rapport au catégories déterminées manuellement).
Pour le Clustering, il s'agit de l'algorithme du k-means qui nous donne les meilleurs résultats pour nos données avec 25% de similarité (donné par ARI) entre les catégories prédites et celles déterminées manuellement.
fichier avec les descriptions nettoyées
text_data.head()
X = text_data.copy()
y = X['category']
X = X.drop(['category'], axis=1)
X = X[X.columns]
# Split en training/testing set.
X_train, X_test, y_train, y_test = train_test_split(X, y,
test_size=0.2 # 20% des données dans le jeu de test = 20% de short data
, random_state=42)
print(X_train.shape, X_test.shape, y_train.shape, y_test.shape)
tfidfvectorizer = TfidfVectorizer(encoding='utf-8',
strip_accents=None,
lowercase=True,
preprocessor=None,
tokenizer=None,
analyzer='word',
stop_words=all_stopwords,
max_df=0.95,
min_df=0.01)
X_train.head()
# convert the dataframe's column into a matrix
X_train_tfidf = tfidfvectorizer.fit_transform(
X_train['description_untokenize_clean'])
X_train_tokens = tfidfvectorizer.get_feature_names()
len(X_train_tokens)
df_tfidf_Xtrain = pd.DataFrame(data=X_train_tfidf.toarray(),
index=X_train.index,
columns=X_train_tokens)
print("\nTD-IDF Vectorizer train test\n")
df_tfidf_Xtrain
X_test.head()
# convert the dataframe's column into a matrix
X_test_tfidf = tfidfvectorizer.transform(
X_test['description_untokenize_clean'])
# retrieve the terms found in the corpora
# if we take same parameters on both Classes(CountVectorizer and TfidfVectorizer),
# it will give same output of get_feature_names() methods)
X_test_tokens = tfidfvectorizer.get_feature_names()
len(X_test_tokens)
df_tfidf_Xtest = pd.DataFrame(data=X_test_tfidf.toarray(),
index=X_test.index,
columns=X_test_tokens)
print("\nTD-IDF Vectorizer\n")
df_tfidf_Xtest
# GridSearch
# create new a knn model
knn2 = KNeighborsClassifier()
# create a dictionary of all values we want to test for n_neighbors
param_grid = {'n_neighbors': np.arange(1, 25)}
score = 'accuracy'
# use gridsearch to test all values for n_neighbors
knn_gscv = GridSearchCV(knn2, param_grid, cv=5)
# Fit sur le training set.
knn_gscv.fit(df_tfidf_Xtrain, y_train)
# Afficher le(s) hyperparamètre(s) optimaux
best_param = knn_gscv.best_estimator_
print("Best paramètres: {}".format(best_param))
# Afficher les performances correspondantes
print("Résultats de la validation croisée :")
for mean, std, params in zip(
knn_gscv.cv_results_['mean_test_score'], # score moyen
knn_gscv.cv_results_['std_test_score'], # écart-type du score
knn_gscv.cv_results_['params'] # valeur de l'hyperparamètre
):
print("{} = {:.3f} (+/-{:.03f}) for {}".format(
score,
mean,
std*2,
params
))
#best_param = KNeighborsClassifier(n_neighbors = 12)
# best_param.fit(X_train,y_train)
#prediction = best_param.predict(X_test)
# Fit the classifier to the data
best_param.fit(df_tfidf_Xtrain, y_train)
prediction = best_param.predict(df_tfidf_Xtest)
knn_class = pd.DataFrame(
{'cat_reel': y_test, 'cat_predit': prediction}).reset_index()
knn_class.head()
knn_class["cat_predit"].nunique()
# Accuracy score : check accuracy of our model on the test data knn.score(X_test, y_test)
accuracy_knn = accuracy_score(y_test, prediction)
print('L\'accuracy score est de {} pour la méthode knn'.format(accuracy_knn))
precision_knn = precision_score(y_test, prediction, average='weighted')
print('Le score de précision est de {} pour la méthode knn'.format(precision_knn))
recall_knn = recall_score(y_test, prediction, average='weighted')
print('Le score recall est de {} pour la méthode Knn'.format(recall_knn))
f1_knn = f1_score(y_test, prediction, average='weighted')
print('Le score f1 est de {} pour la méthode Knn'.format(f1_knn))
le = preprocessing.LabelEncoder()
knn_class['label_cat_reel'] = le.fit_transform(
np.array(knn_class['cat_reel']))
knn_class['label_cat_predit'] = le.transform(
np.array(knn_class['cat_predit']))
knn_class
plt.figure(figsize=(10, 10))
plt.title('Répartition des catégories après classification par knn',
fontsize=18, fontweight="bold")
sns.distplot(knn_class['label_cat_reel'], bins=20, rug=True)
sns.distplot(knn_class['label_cat_predit'], bins=20, rug=True)
plt.xlabel("Catégories")
plt.legend(['reel', 'prediction'])
Les labels des categories prédites correspondent aux labels des catégories réelles pour les donnéees test. Avec ce graphique, on peut voir que les catégories prédites sont proches de la classification manuelle. On peut voir aussi que pour les catégories 3 et 4 (homedecor&_festive_needs, home_furnishing), la classification par knn regroupe moins de produits dans ces catégories par rapport à la réalité. La conclusion inverse peut être faîte pour les catégories 0, 2 et 5 (baby_care, computers, kitchen_and_dining). Pour les catégories 1 et 6 (beauty_and_personal_care, watches) la classification par knn regroupe autant de produits dans ces catégories par rapport à la réalité.
fig = plt.figure(figsize=(30, 15))
ax1 = plt.subplot(121)
ax2 = plt.subplot(122)
nb_label_real = knn_class['cat_reel'].nunique()
cs_nb_label_real = cm.Set2(np.arange(nb_label_real))
nb_label_predit = knn_class['cat_predit'].nunique()
cs_nb_label_predit = cm.Set2(np.arange(nb_label_predit))
ax1 = knn_class.groupby('cat_reel').count().plot(kind='pie',
y='label_cat_reel',
ax=ax1,
legend=False,
shadow=True,
startangle=0,
autopct='%1.1f%%',
textprops={'fontsize': 26},
colors = cs_nb_label_real)
ax1.axis('equal')
ax1.set_xlabel("manuellement", fontsize=22)
ax1.set_ylabel(" ")
ax2 = knn_class.groupby('cat_predit').count().plot(kind='pie',
y='label_cat_predit',
ax=ax2,
legend=False,
shadow=True,
startangle=0,
autopct='%1.1f%%',
textprops={'fontsize': 26},
colors = cs_nb_label_predit)
ax2.axis('equal')
ax2.set_xlabel("par classification knn", fontsize=22)
ax2.set_ylabel(" ")
plt.suptitle('Répartition des produits (test data) entre les différentes catégories déterminées',
fontsize=30)
plt.subplots_adjust(wspace=1.5)
plt.show()
Une matrice de confusion permet de mesurer la qualité de notre système de classification et de voir facilement les mauvaises répartitions entre les catégories.
cf_matrix = confusion_matrix(
knn_class["cat_reel"], knn_class["cat_predit"], normalize='true')
plt.figure(figsize=(18, 12))
plt.title('Matrice de confusion', size=20, weight='bold')
sns.set(font_scale=1.4) # for label size
sns.heatmap(cf_matrix, annot=True, # cf_matrix/np.sum(cf_matrix)=normalize=all
fmt='.2%', cmap='BuPu',
annot_kws={"size": 15, 'weight': 'bold'})
plt.ylabel("Vraie catégorie", weight='bold', size=20)
plt.xlabel('Prediction', weight='bold', size=17)
plt.show()
L'algorithme se trompe surtout sur la prédiction des catégories 0, 3 et 4.
classification_resume = pd.DataFrame({})
classification_resume = classification_resume.append(
pd.DataFrame(
{
"Algorithme": "knn",
"accuracy": accuracy_knn,
"precision": precision_knn,
"recall": recall_knn,
"f1_score": f1_knn
},
index=[0],
)
)
classification_resume
# GridSearch
param_grid = [{'max_depth': range(2, 10, 2)}]
gridSearch = GridSearchCV(RandomForestClassifier(),
param_grid, cv=5, scoring='accuracy')
score = 'accuracy'
# Fit sur le training set.
gridSearch.fit(df_tfidf_Xtrain, y_train)
# Afficher le(s) hyperparamètre(s) optimaux
best_param = gridSearch.best_estimator_
print("Best paramètres: {}".format(best_param))
# Afficher les performances correspondantes
print("Résultats de la validation croisée :")
for mean, std, params in zip(
gridSearch.cv_results_['mean_test_score'], # score moyen
gridSearch.cv_results_['std_test_score'], # écart-type du score
gridSearch.cv_results_['params'] # valeur de l'hyperparamètre
):
print("{} = {:.3f} (+/-{:.03f}) for {}".format(
score,
mean,
std*2,
params
))
accuracy, precision, recall, f1, resume_class = classification(
best_param, df_tfidf_Xtrain, y_train, df_tfidf_Xtest, y_test, 'Random Forest')
Ici le score d'accuracy est de 0.83 ce qui montre que la classification fonctionne bien avec une très grande précision.
Ici le score de précision est de 0.85 montre que la prédiction est assez précise car le score est proche de 1.
Ici le score de recall est de 0.83 montre que la prédiction fait en majorité des vrais positifs car le score est proche de 1.
Les labels des catégories prédites correspondent aux labels des catégories réelles pour les donnéees test. Avec ce graphique, on peut voir que les catégories prédites sont proches de la classification manuelle. On peut voir aussi que pour les catégories 0, 2, 3 et 5, la classification regroupe moins de produits dans ces catégories par rapport à la réalité. La conclusion inverse peut être faîte pour les catégories 4 et 6. La classification refroupe autant de produits qu'en réalité pour la catégorie 1.
Une matrice de confusion permet de mesurer la qualité de notre système de classification et de voir facilement les mauvaises répartitions entre catégories.
L'algorithme se trompe surtout sur la classification des catégories 0, 2 et 3.
classification_resume = classification_resume.append(
pd.DataFrame(
{
"Algorithme": "Random Forest",
"accuracy": accuracy,
"precision": precision,
"recall": recall,
"f1_score": f1
},
index=[1],
)
)
classification_resume
# GridSearch
param_grid = {'alpha': [0.0001, 0.001, 0.01, 0.1, 0.5, 1.0, 10.0]
}
gridSearch = GridSearchCV(MultinomialNB(), param_grid,
cv=5, scoring='accuracy')
score = 'accuracy'
# Fit sur le training set.
gridSearch.fit(df_tfidf_Xtrain, y_train)
# Afficher le(s) hyperparamètre(s) optimaux
best_param = gridSearch.best_estimator_
print("Best paramètres: {}".format(best_param))
# Afficher les performances correspondantes
print("Résultats de la validation croisée :")
for mean, std, params in zip(
gridSearch.cv_results_['mean_test_score'], # score moyen
gridSearch.cv_results_['std_test_score'], # écart-type du score
gridSearch.cv_results_['params'] # valeur de l'hyperparamètre
):
print("{} = {:.3f} (+/-{:.03f}) for {}".format(
score,
mean,
std*2,
params
))
alpha représente le paramètre de lissage.
accuracy, precision, recall, f1, resume_class = classification(
best_param, df_tfidf_Xtrain, y_train, df_tfidf_Xtest, y_test, 'MultinomialNB')
Ici le score d'accuracy est de 0.87 ce qui montre que la classification fonctionne bien avec une précision très grande.
Ici le score de précision est de 0.89 montre que la prédiction est assez précise car le score est proche de 1.
Ici le score de f1 est de 0.87 montre que la prédiction fait en majorité des vrais positifs car le score est proche de 1.
Les labels des catégories prédites correspondent aux labels des catégories réelles pour les donnéees test. Avec ce graphique, on peut voir que les catégories prédites sont proches de la classification manuelle. On peut voir aussi que pour les categories 0, 2 et 5, la classification regroupe moins de produits dans ces catégorie par rapport à la réalité. La conclusion inverse peut être faîte pour les catégories 1, 3 et 4. L'algorithme de prédiction associe à la catégorie 6 le même nombre de produit.
L'algorithme se trompe surtout sur la prédiction des catégories 0 et 2.
classification_resume = classification_resume.append(
pd.DataFrame(
{
"Algorithme": "MultinomialNB",
"accuracy": accuracy,
"precision": precision,
"recall": recall,
"f1_score": f1
},
index=[2],
)
)
classification_resume